﻿using System;
using System.Reflection;
using System.ServiceModel;
using Castle.DynamicProxy;
using Framework.Core;
using Framework.Remoting.Conventions;

namespace Framework.Remoting.Client
{
    public class WcfInterceptor<TContract> : IInterceptor, IDisposable
    {
        private readonly IRequest request;
        private readonly WcfServiceInfo wcfServiceInfo;

        public WcfInterceptor(EnvMode envMode, IRequest request,
                              IWcfServiceInfoFactory wcfServiceInfoFactory)
        {
            this.request = request;
            wcfServiceInfo = wcfServiceInfoFactory.Create<TContract>(envMode);
        }


        public void Dispose()
        {
            request.Dispose();
        }

        public void Intercept(IInvocation invocation)
        {
            try
            {
                Invoke(invocation);
            }
            catch (FaultException)
            {
                throw;
            }
            catch (CommunicationException)
            {
                try
                {
                    Invoke(invocation);
                }
                catch (Exception e)
                {
                    throw new WcfProxyException("Invocation error, see InnerException for more details", e);
                }
            }
        }



        private void Invoke(IInvocation invocation)
        {
            try
            {
                invocation.ReturnValue = request.Invoke<TContract>(c => invocation.Method.Invoke(c, invocation.Arguments), wcfServiceInfo);
            }
            catch (TargetInvocationException e)
            {
                var faultException = e.InnerException as FaultException;
                if (faultException == null)
                {
                    throw e.InnerException;
                }
                if (!IsGenericFaultException(faultException))
                {
                    throw new MissingFaultContractDeclarationException(faultException.ToString());
                }
                throw faultException;
            }
        }

        private static bool IsGenericFaultException(FaultException faultException)
        {
            var faultType = faultException.GetType();
            return faultType.IsGenericType &&
                   typeof(FaultException<>).IsAssignableFrom(faultType.GetGenericTypeDefinition());
        }
    }
}